home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / Pakiet bezpieczenstwa / mini Pentoo LiveCD 2006.1 / mpentoo-2006.1.iso / livecd.squashfs / usr / lib / python2.4 / idlelib / TreeWidget.py < prev    next >
Text File  |  2005-10-18  |  15KB  |  479 lines

  1. # XXX TO DO:
  2. # - popup menu
  3. # - support partial or total redisplay
  4. # - key bindings (instead of quick-n-dirty bindings on Canvas):
  5. #   - up/down arrow keys to move focus around
  6. #   - ditto for page up/down, home/end
  7. #   - left/right arrows to expand/collapse & move out/in
  8. # - more doc strings
  9. # - add icons for "file", "module", "class", "method"; better "python" icon
  10. # - callback for selection???
  11. # - multiple-item selection
  12. # - tooltips
  13. # - redo geometry without magic numbers
  14. # - keep track of object ids to allow more careful cleaning
  15. # - optimize tree redraw after expand of subnode
  16.  
  17. import os
  18. import sys
  19. from Tkinter import *
  20. import imp
  21.  
  22. import ZoomHeight
  23. from configHandler import idleConf
  24.  
  25. ICONDIR = "Icons"
  26.  
  27. # Look for Icons subdirectory in the same directory as this module
  28. try:
  29.     _icondir = os.path.join(os.path.dirname(__file__), ICONDIR)
  30. except NameError:
  31.     _icondir = ICONDIR
  32. if os.path.isdir(_icondir):
  33.     ICONDIR = _icondir
  34. elif not os.path.isdir(ICONDIR):
  35.     raise RuntimeError, "can't find icon directory (%r)" % (ICONDIR,)
  36.  
  37. def listicons(icondir=ICONDIR):
  38.     """Utility to display the available icons."""
  39.     root = Tk()
  40.     import glob
  41.     list = glob.glob(os.path.join(icondir, "*.gif"))
  42.     list.sort()
  43.     images = []
  44.     row = column = 0
  45.     for file in list:
  46.         name = os.path.splitext(os.path.basename(file))[0]
  47.         image = PhotoImage(file=file, master=root)
  48.         images.append(image)
  49.         label = Label(root, image=image, bd=1, relief="raised")
  50.         label.grid(row=row, column=column)
  51.         label = Label(root, text=name)
  52.         label.grid(row=row+1, column=column)
  53.         column = column + 1
  54.         if column >= 10:
  55.             row = row+2
  56.             column = 0
  57.     root.images = images
  58.  
  59.  
  60. class TreeNode:
  61.  
  62.     def __init__(self, canvas, parent, item):
  63.         self.canvas = canvas
  64.         self.parent = parent
  65.         self.item = item
  66.         self.state = 'collapsed'
  67.         self.selected = False
  68.         self.children = []
  69.         self.x = self.y = None
  70.         self.iconimages = {} # cache of PhotoImage instances for icons
  71.  
  72.     def destroy(self):
  73.         for c in self.children[:]:
  74.             self.children.remove(c)
  75.             c.destroy()
  76.         self.parent = None
  77.  
  78.     def geticonimage(self, name):
  79.         try:
  80.             return self.iconimages[name]
  81.         except KeyError:
  82.             pass
  83.         file, ext = os.path.splitext(name)
  84.         ext = ext or ".gif"
  85.         fullname = os.path.join(ICONDIR, file + ext)
  86.         image = PhotoImage(master=self.canvas, file=fullname)
  87.         self.iconimages[name] = image
  88.         return image
  89.  
  90.     def select(self, event=None):
  91.         if self.selected:
  92.             return
  93.         self.deselectall()
  94.         self.selected = True
  95.         self.canvas.delete(self.image_id)
  96.         self.drawicon()
  97.         self.drawtext()
  98.  
  99.     def deselect(self, event=None):
  100.         if not self.selected:
  101.             return
  102.         self.selected = False
  103.         self.canvas.delete(self.image_id)
  104.         self.drawicon()
  105.         self.drawtext()
  106.  
  107.     def deselectall(self):
  108.         if self.parent:
  109.             self.parent.deselectall()
  110.         else:
  111.             self.deselecttree()
  112.  
  113.     def deselecttree(self):
  114.         if self.selected:
  115.             self.deselect()
  116.         for child in self.children:
  117.             child.deselecttree()
  118.  
  119.     def flip(self, event=None):
  120.         if self.state == 'expanded':
  121.             self.collapse()
  122.         else:
  123.             self.expand()
  124.         self.item.OnDoubleClick()
  125.         return "break"
  126.  
  127.     def expand(self, event=None):
  128.         if not self.item._IsExpandable():
  129.             return
  130.         if self.state != 'expanded':
  131.             self.state = 'expanded'
  132.             self.update()
  133.             self.view()
  134.  
  135.     def collapse(self, event=None):
  136.         if self.state != 'collapsed':
  137.             self.state = 'collapsed'
  138.             self.update()
  139.  
  140.     def view(self):
  141.         top = self.y - 2
  142.         bottom = self.lastvisiblechild().y + 17
  143.         height = bottom - top
  144.         visible_top = self.canvas.canvasy(0)
  145.         visible_height = self.canvas.winfo_height()
  146.         visible_bottom = self.canvas.canvasy(visible_height)
  147.         if visible_top <= top and bottom <= visible_bottom:
  148.             return
  149.         x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
  150.         if top >= visible_top and height <= visible_height:
  151.             fraction = top + height - visible_height
  152.         else:
  153.             fraction = top
  154.         fraction = float(fraction) / y1
  155.         self.canvas.yview_moveto(fraction)
  156.  
  157.     def lastvisiblechild(self):
  158.         if self.children and self.state == 'expanded':
  159.             return self.children[-1].lastvisiblechild()
  160.         else:
  161.             return self
  162.  
  163.     def update(self):
  164.         if self.parent:
  165.             self.parent.update()
  166.         else:
  167.             oldcursor = self.canvas['cursor']
  168.             self.canvas['cursor'] = "watch"
  169.             self.canvas.update()
  170.             self.canvas.delete(ALL)     # XXX could be more subtle
  171.             self.draw(7, 2)
  172.             x0, y0, x1, y1 = self.canvas.bbox(ALL)
  173.             self.canvas.configure(scrollregion=(0, 0, x1, y1))
  174.             self.canvas['cursor'] = oldcursor
  175.  
  176.     def draw(self, x, y):
  177.         # XXX This hard-codes too many geometry constants!
  178.         self.x, self.y = x, y
  179.         self.drawicon()
  180.         self.drawtext()
  181.         if self.state != 'expanded':
  182.             return y+17
  183.         # draw children
  184.         if not self.children:
  185.             sublist = self.item._GetSubList()
  186.             if not sublist:
  187.                 # _IsExpandable() was mistaken; that's allowed
  188.                 return y+17
  189.             for item in sublist:
  190.                 child = self.__class__(self.canvas, self, item)
  191.                 self.children.append(child)
  192.         cx = x+20
  193.         cy = y+17
  194.         cylast = 0
  195.         for child in self.children:
  196.             cylast = cy
  197.             self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
  198.             cy = child.draw(cx, cy)
  199.             if child.item._IsExpandable():
  200.                 if child.state == 'expanded':
  201.                     iconname = "minusnode"
  202.                     callback = child.collapse
  203.                 else:
  204.                     iconname = "plusnode"
  205.                     callback = child.expand
  206.                 image = self.geticonimage(iconname)
  207.                 id = self.canvas.create_image(x+9, cylast+7, image=image)
  208.                 # XXX This leaks bindings until canvas is deleted:
  209.                 self.canvas.tag_bind(id, "<1>", callback)
  210.                 self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
  211.         id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
  212.             ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
  213.             fill="gray50")
  214.         self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
  215.         return cy
  216.  
  217.     def drawicon(self):
  218.         if self.selected:
  219.             imagename = (self.item.GetSelectedIconName() or
  220.                          self.item.GetIconName() or
  221.                          "openfolder")
  222.         else:
  223.             imagename = self.item.GetIconName() or "folder"
  224.         image = self.geticonimage(imagename)
  225.         id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
  226.         self.image_id = id
  227.         self.canvas.tag_bind(id, "<1>", self.select)
  228.         self.canvas.tag_bind(id, "<Double-1>", self.flip)
  229.  
  230.     def drawtext(self):
  231.         textx = self.x+20-1
  232.         texty = self.y-1
  233.         labeltext = self.item.GetLabelText()
  234.         if labeltext:
  235.             id = self.canvas.create_text(textx, texty, anchor="nw",
  236.                                          text=labeltext)
  237.             self.canvas.tag_bind(id, "<1>", self.select)
  238.             self.canvas.tag_bind(id, "<Double-1>", self.flip)
  239.             x0, y0, x1, y1 = self.canvas.bbox(id)
  240.             textx = max(x1, 200) + 10
  241.         text = self.item.GetText() or "<no text>"
  242.         try:
  243.             self.entry
  244.         except AttributeError:
  245.             pass
  246.         else:
  247.             self.edit_finish()
  248.         try:
  249.             label = self.label
  250.         except AttributeError:
  251.             # padding carefully selected (on Windows) to match Entry widget:
  252.             self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
  253.         theme = idleConf.GetOption('main','Theme','name')
  254.         if self.selected:
  255.             self.label.configure(idleConf.GetHighlight(theme, 'hilite'))
  256.         else:
  257.             self.label.configure(idleConf.GetHighlight(theme, 'normal'))
  258.         id = self.canvas.create_window(textx, texty,
  259.                                        anchor="nw", window=self.label)
  260.         self.label.bind("<1>", self.select_or_edit)
  261.         self.label.bind("<Double-1>", self.flip)
  262.         self.text_id = id
  263.  
  264.     def select_or_edit(self, event=None):
  265.         if self.selected and self.item.IsEditable():
  266.             self.edit(event)
  267.         else:
  268.             self.select(event)
  269.  
  270.     def edit(self, event=None):
  271.         self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
  272.         self.entry.insert(0, self.label['text'])
  273.         self.entry.selection_range(0, END)
  274.         self.entry.pack(ipadx=5)
  275.         self.entry.focus_set()
  276.         self.entry.bind("<Return>", self.edit_finish)
  277.         self.entry.bind("<Escape>", self.edit_cancel)
  278.  
  279.     def edit_finish(self, event=None):
  280.         try:
  281.             entry = self.entry
  282.             del self.entry
  283.         except AttributeError:
  284.             return
  285.         text = entry.get()
  286.         entry.destroy()
  287.         if text and text != self.item.GetText():
  288.             self.item.SetText(text)
  289.         text = self.item.GetText()
  290.         self.label['text'] = text
  291.         self.drawtext()
  292.         self.canvas.focus_set()
  293.  
  294.     def edit_cancel(self, event=None):
  295.         try:
  296.             entry = self.entry
  297.             del self.entry
  298.         except AttributeError:
  299.             return
  300.         entry.destroy()
  301.         self.drawtext()
  302.         self.canvas.focus_set()
  303.  
  304.  
  305. class TreeItem:
  306.  
  307.     """Abstract class representing tree items.
  308.  
  309.     Methods should typically be overridden, otherwise a default action
  310.     is used.
  311.  
  312.     """
  313.  
  314.     def __init__(self):
  315.         """Constructor.  Do whatever you need to do."""
  316.  
  317.     def GetText(self):
  318.         """Return text string to display."""
  319.  
  320.     def GetLabelText(self):
  321.         """Return label text string to display in front of text (if any)."""
  322.  
  323.     expandable = None
  324.  
  325.     def _IsExpandable(self):
  326.         """Do not override!  Called by TreeNode."""
  327.         if self.expandable is None:
  328.             self.expandable = self.IsExpandable()
  329.         return self.expandable
  330.  
  331.     def IsExpandable(self):
  332.         """Return whether there are subitems."""
  333.         return 1
  334.  
  335.     def _GetSubList(self):
  336.         """Do not override!  Called by TreeNode."""
  337.         if not self.IsExpandable():
  338.             return []
  339.         sublist = self.GetSubList()
  340.         if not sublist:
  341.             self.expandable = 0
  342.         return sublist
  343.  
  344.     def IsEditable(self):
  345.         """Return whether the item's text may be edited."""
  346.  
  347.     def SetText(self, text):
  348.         """Change the item's text (if it is editable)."""
  349.  
  350.     def GetIconName(self):
  351.         """Return name of icon to be displayed normally."""
  352.  
  353.     def GetSelectedIconName(self):
  354.         """Return name of icon to be displayed when selected."""
  355.  
  356.     def GetSubList(self):
  357.         """Return list of items forming sublist."""
  358.  
  359.     def OnDoubleClick(self):
  360.         """Called on a double-click on the item."""
  361.  
  362.  
  363. # Example application
  364.  
  365. class FileTreeItem(TreeItem):
  366.  
  367.     """Example TreeItem subclass -- browse the file system."""
  368.  
  369.     def __init__(self, path):
  370.         self.path = path
  371.  
  372.     def GetText(self):
  373.         return os.path.basename(self.path) or self.path
  374.  
  375.     def IsEditable(self):
  376.         return os.path.basename(self.path) != ""
  377.  
  378.     def SetText(self, text):
  379.         newpath = os.path.dirname(self.path)
  380.         newpath = os.path.join(newpath, text)
  381.         if os.path.dirname(newpath) != os.path.dirname(self.path):
  382.             return
  383.         try:
  384.             os.rename(self.path, newpath)
  385.             self.path = newpath
  386.         except os.error:
  387.             pass
  388.  
  389.     def GetIconName(self):
  390.         if not self.IsExpandable():
  391.             return "python" # XXX wish there was a "file" icon
  392.  
  393.     def IsExpandable(self):
  394.         return os.path.isdir(self.path)
  395.  
  396.     def GetSubList(self):
  397.         try:
  398.             names = os.listdir(self.path)
  399.         except os.error:
  400.             return []
  401.         names.sort(lambda a, b: cmp(os.path.normcase(a), os.path.normcase(b)))
  402.         sublist = []
  403.         for name in names:
  404.             item = FileTreeItem(os.path.join(self.path, name))
  405.             sublist.append(item)
  406.         return sublist
  407.  
  408.  
  409. # A canvas widget with scroll bars and some useful bindings
  410.  
  411. class ScrolledCanvas:
  412.     def __init__(self, master, **opts):
  413.         if not opts.has_key('yscrollincrement'):
  414.             opts['yscrollincrement'] = 17
  415.         self.master = master
  416.         self.frame = Frame(master)
  417.         self.frame.rowconfigure(0, weight=1)
  418.         self.frame.columnconfigure(0, weight=1)
  419.         self.canvas = Canvas(self.frame, **opts)
  420.         self.canvas.grid(row=0, column=0, sticky="nsew")
  421.         self.vbar = Scrollbar(self.frame, name="vbar")
  422.         self.vbar.grid(row=0, column=1, sticky="nse")
  423.         self.hbar = Scrollbar(self.frame, name="hbar", orient="horizontal")
  424.         self.hbar.grid(row=1, column=0, sticky="ews")
  425.         self.canvas['yscrollcommand'] = self.vbar.set
  426.         self.vbar['command'] = self.canvas.yview
  427.         self.canvas['xscrollcommand'] = self.hbar.set
  428.         self.hbar['command'] = self.canvas.xview
  429.         self.canvas.bind("<Key-Prior>", self.page_up)
  430.         self.canvas.bind("<Key-Next>", self.page_down)
  431.         self.canvas.bind("<Key-Up>", self.unit_up)
  432.         self.canvas.bind("<Key-Down>", self.unit_down)
  433.         #if isinstance(master, Toplevel) or isinstance(master, Tk):
  434.         self.canvas.bind("<Alt-Key-2>", self.zoom_height)
  435.         self.canvas.focus_set()
  436.     def page_up(self, event):
  437.         self.canvas.yview_scroll(-1, "page")
  438.         return "break"
  439.     def page_down(self, event):
  440.         self.canvas.yview_scroll(1, "page")
  441.         return "break"
  442.     def unit_up(self, event):
  443.         self.canvas.yview_scroll(-1, "unit")
  444.         return "break"
  445.     def unit_down(self, event):
  446.         self.canvas.yview_scroll(1, "unit")
  447.         return "break"
  448.     def zoom_height(self, event):
  449.         ZoomHeight.zoom_height(self.master)
  450.         return "break"
  451.  
  452.  
  453. # Testing functions
  454.  
  455. def test():
  456.     import PyShell
  457.     root = Toplevel(PyShell.root)
  458.     root.configure(bd=0, bg="yellow")
  459.     root.focus_set()
  460.     sc = ScrolledCanvas(root, bg="white", highlightthickness=0, takefocus=1)
  461.     sc.frame.pack(expand=1, fill="both")
  462.     item = FileTreeItem("C:/windows/desktop")
  463.     node = TreeNode(sc.canvas, None, item)
  464.     node.expand()
  465.  
  466. def test2():
  467.     # test w/o scrolling canvas
  468.     root = Tk()
  469.     root.configure(bd=0)
  470.     canvas = Canvas(root, bg="white", highlightthickness=0)
  471.     canvas.pack(expand=1, fill="both")
  472.     item = FileTreeItem(os.curdir)
  473.     node = TreeNode(canvas, None, item)
  474.     node.update()
  475.     canvas.focus_set()
  476.  
  477. if __name__ == '__main__':
  478.     test()
  479.